跳到主要内容

Lua 脚本语言学习

Lua 是什么?

Lua (LOO-ah) 是一种可嵌入、轻量、快速、功能强大的脚本语言。它支持过程式编程、 面向对象编程、函数式编程、数据驱动编程和数据描述(data description)。

Lua 将简洁的过程式语法和基于关联数组、可扩展语义的数据描述语法结构结合了起来。 Lua 是动态类型的语言,它使用基于寄存器的虚拟机解释和运行字节码(bytecode),并 使用增量垃级回收(incremental garbage collection)机制自动管理内存。这些特点使 得 Lua 很适合用于配置、脚本化和快速构造原型的场景。

Lua 应用场景

  • 游戏开发
  • 独立应用脚本
  • Web 应用脚本
  • 扩展和数据库插件如:MySQL Proxy 和 MySQL WorkBench
  • 安全系统,如入侵检测系统

安装环境

curl -R -O http://www.lua.org/ftp/lua-5.4.3.tar.gz
tar zxf lua-5.4.3.tar.gz
cd lua-5.4.3

# 编译安装
make linux test
make install

接下来创建一个 HelloWorld.lua 文件,代码如下:

print("Hello World!")

执行以下命令

$ lua HelloWorld.lua

脚本式编程

在开头添加这个指定解释器的位置,加上 # 号标记解释器会忽略它

#!/usr/local/bin/lua

print("Hello World!")

给这个文件添加可执行权限

chmod a+x temp.lua

# 然后执行的时候要加上 ./
./temp.lua

注释

单行注释

--

多行注释

--[[
多行注释
多行注释
--]]

Lua 变量

变量在使用前,需要在代码中进行声明,即创建该变量。

Lua 变量有三种类型:全局变量、局部变量、表中的域。

Lua 中的变量 全是全局变量,哪怕是语句块或是函数里,除非用 local 显式声明为局部变量。

局部变量的作用域为从声明位置开始到所在语句块结束。变量的默认值均为 nil

-- test.lua 文件脚本
a = 5 -- 全局变量
local b = 5 -- 局部变量

function joke()
c = 5 -- 全局变量
local d = 6 -- 局部变量
end

joke()
print(c,d) --> 5 nil

do
local a = 6 -- 局部变量
b = 6 -- 对局部变量重新赋值
print(a,b); --> 6 6
end

print(a,b) --> 5 6

输出结果如下:

5    nil
6 6
5 6

访问一个没有初始化的全局变量也不会出错,只不过得到的结果是:nil

> print(b)
nil
> b=10
> print(b)
10
>

如果你想删除一个全局变量,只需要将变量赋值为 nil

b = nil
print(b) --> nil

这样变量b就好像从没被使用过一样。换句话说, 当且仅当一个变量不等于 nil 时,这个变量即存在。

赋值语句

赋值是改变一个变量的值和改变表域的最基本的方法。

a = "hello" .. "world"
t.n = t.n + 1

Lua 可以对多个变量同时赋值,变量列表和值列表的各个元素用逗号分开,赋值语句右边的值会依次赋给左边的变量。

a, b = 10, 2 * x      
-- 等价于:
a = 10; b = 2 * x

遇到赋值语句 Lua 会先计算右边所有的值然后再执行赋值操作,所以我们可以这样进行交换变量的值:

x, y = y, x                     -- swap 'x' for 'y'
a[i], a[j] = a[j], a[i] -- swap 'a[i]' for 'a[j]'

Lua 数据类型

Lua 是动态类型语言,变量不要类型定义,只需要为变量赋值。 值可以存储在变量中,作为参数传递或结果返回。

Lua 中有 8 个基本类型分别为:nil、boolean、number、string、userdata、function、thread 和 table。

可以使用 type 函数测试给定变量或者值的类型:

print(type("Hello world"))      --> string
print(type(10.4*3)) --> number(只有一种,浮点整形都是它)
print(type(print)) --> function
print(type(type)) --> function
print(type(true)) --> boolean
print(type(nil)) --> nil
print(type(type(X))) --> string

这里就只介绍不一样的类型,其它和其它语言一样的类型就不做介绍了

nil(空)类型

nil 类型表示一种没有任何有效值,它只有一个值 -- nil,例如打印一个没有赋值的变量,便会输出一个 nil 值:

> print(type(a))
nil
>

对于全局变量和 table,nil 还有一个 "删除" 作用,给全局变量或者 table 表里的变量赋一个 nil 值,等同于把它们删掉,执行下面代码就知:

tab1 = { key1 = "val1", key2 = "val2", "val3" }
for k, v in pairs(tab1) do
print(k .. " - " .. v)
end

tab1.key1 = nil
for k, v in pairs(tab1) do
print(k .. " - " .. v)
end

nil 作比较时应该加上双引号 "

> type(X)
nil
> type(X)==nil
false
> type(X)=="nil"
true
>

type(X)==nil 结果为 false 的原因是 type(X) 实质是返回的 "nil" 字符串,是一个 string 类型:

type(type(X))==string

string(字符串)

字符串由一对双引号或单引号来表示。

string1 = "this is string1"
string2 = 'this is string2'

也可以用 2 个方括号 "[[]]" 来表示"一块"字符串。

html = [[
<html>
<head></head>
<body>
<h1>Hello World!</h1>
</body>
</html>
]]
print(html)

在对一个数字字符串上进行算术操作时,Lua 会尝试 将这个数字字符串转成一个数字:

> print("2" + 6)
8.0
> print("2" + "6")
8.0
> print("2 + 6")
2 + 6
> print("-2e2" * "6")
-1200.0
> print("error" + 1)
stdin:1: attempt to perform arithmetic on a string value
stack traceback:
stdin:1: in main chunk
[C]: in ?
>

以上代码中 "error" + 1 执行报错了,字符串连接使用的是 .. ,如:

> print("a" .. 'b')
ab
> print(157 .. 428)
157428
>

使用 # 来计算字符串的长度,放在字符串前面,如下实例:

> len = "www.example.com"
> print(#len)
15
> print(#"www.example.com")
15
>

table(表)

在 Lua 里,table 的创建是通过 "构造表达式" 来完成,最简单构造表达式是 {},用来创建一个空表。也可以在表里添加一些数据,直接初始化表:

-- 创建一个空的 table
local tbl1 = {}

-- 直接初始表
local tbl2 = {"apple", "pear", "orange", "grape"}

Lua 中的表(table)其实是一个"关联数组"(associative arrays),数组的索引可以是数字或者是字符串。

-- table_test.lua 脚本文件
a = {}
a["key"] = "value"
key = 10
a[key] = 22
a[key] = a[key] + 11

for k, v in pairs(a) do
print(k .. " : " .. v)
end

脚本执行结果为:

key : value
10 : 33

不同于其他语言的数组把 0 作为数组的初始索引,在 Lua 里表的默认初始索引一般以 1 开始。

-- table_test2.lua 脚本文件
local tbl = {"apple", "pear", "orange", "grape"}

for key, val in pairs(tbl) do
print("Key", key)
end

脚本执行结果为:

Key    1
Key 2
Key 3
Key 4

table 不会固定长度大小,有新数据添加时 table 长度会自动增长,没初始的 table 都是 nil。

-- table_test3.lua 脚本文件
a3 = {}

for i = 1, 10 do
a3[i] = i
end

a3["key"] = "val"
print(a3["key"])
print(a3["none"])

脚本执行结果为:

val
nil

对 table 的索引使用方括号 []。 Lua 也提供了 . 操作。

t[i]
t.i -- 当索引为字符串类型时的一种简化写法
gettable_event(t,i) -- 采用索引访问本质上是一个类似这样的函数调用

function(函数)

在 Lua 中,函数是被看作是"第一类值(First-Class Value)",函数可以存在变量里:

-- function_test.lua 脚本文件
-- 斐波那契数列
function Factorial1(n)
if n == 0 then
return 1
else
return n * Factorial1(n - 1)
end
end

print(Factorial1(5))
Factorial2 = Factorial1
print(Factorial2(5))

执行结果如下:

120
120

回调函数

function 可以以匿名函数(anonymous function)的方式通过参数传递:

-- function_test2.lua 脚本文件
function testFun(tab, fun)
for k, v in pairs(tab) do
print(fun(k, v));
end
end

tab = {
key1 = "val1",
key2 = "val2"
};

testFun(tab, function(key, val) -- 匿名函数
return key .. "=" .. val;
end);

输出:

key1 = val1
key2 = val2

Lua 循环语句

Lua 语言提供了以下几种循环处理方式:

循环控制语句用于控制程序的流程,以实现程序的各种结构方式。

-- table_test2.lua 脚本文件
local tbl = {"apple", "pear", "orange", "grape"}

for key, val in pairs(tbl) do
print("Key", key)
end

Lua 支持以下循环控制语句:

控制语句描述
break 语句退出当前循环或语句,并开始脚本执行紧接着的语句。
goto 语句将程序的控制点转移到一个标签处。

死循环:

while( true )
do
print("循环将永远执行下去")
end

For 的使用

for var = exp1,exp2,exp3 do  
<执行体>
end

var 从 exp1 变化到 exp2,每次变化以 exp3 为步长递增 var,并执行一次 "执行体"。exp3 是可选的,如果不指定,默认为1。

for i=1,f(x) do
print(i)
end

for i=10,1,-1 do
print(i)
end

for 的三个表达式在循环开始前一次性求值,以后不再进行求值。比如上面的 f(x) 只会在循环开始前执行一次,其结果用在后面的循环中。

如下所示:

#!/usr/local/bin/lua  
function f(x)
print("function")
return x*2
end


for i=1,f(5) do print(i)
end

以上实例输出结果为:

function
1
2
3
4
5
6
7
8
9
10

可以看到 函数 f(x) 只在循环开始前执行一次。

ipairs 迭代器的使用

泛型 for 循环通过一个迭代器函数来遍历所有值,类似 java 中的 foreach 语句。

Lua 编程语言中泛型 for 循环语法格式:

--打印数组a的所有值  
a = {"one", "two", "three"}
for i, v in ipairs(a) do
print(i, v)
end

i 是数组索引值,v 是对应索引的数组元素值。ipairs 是 Lua 提供的一个迭代器函数,用来迭代数组。

如下实例:

#!/usr/local/bin/lua  
days = {"Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"}

for i,v in ipairs(days) do print(v) end

以上实例输出结果为:

Sunday
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday

判断语句

控制结构的条件表达式结果可以是任何值,Lua 认为 false 和 nil 为假,true 和非 nil 为真(所以 0 也为 true)

--[ 0 为 true ]
if(0)
then
print("0 为 true")
end

以上代码输出结果为:

0 为 true

Lua if 语句语法格式如下:

if(布尔表达式)
then
--[ 在布尔表达式为 true 时执行的语句 --]
end

如下所示:

--[ 定义变量 --]
a = 10;

--[ 使用 if 语句 --]
if( a < 20 )
then
--[ if 条件为 true 时打印以下信息 --]
print("a 小于 20" );
end
print("a 的值为:", a);

if else 语句

if(布尔表达式)
then
--[ 布尔表达式为 true 时执行该语句块 --]
else
--[ 布尔表达式为 false 时执行该语句块 --]
end

if...elseif...else 语句

if( 布尔表达式 1)
then
--[ 在布尔表达式 1 为 true 时执行该语句块 --]

elseif( 布尔表达式 2)
then
--[ 在布尔表达式 2 为 true 时执行该语句块 --]

elseif( 布尔表达式 3)
then
--[ 在布尔表达式 3 为 true 时执行该语句块 --]
else
--[ 如果以上布尔表达式都不为 true 则执行该语句块 --]
end

嵌套判断:

if( 布尔表达式 1)
then
--[ 布尔表达式 1 为 true 时执行该语句块 --]
if(布尔表达式 2)
then
--[ 布尔表达式 2 为 true 时执行该语句块 --]
end
end

模块

模块类似于一个封装库,从 Lua 5.1 开始,Lua 加入了标准的模块管理机制,可以把一些公用的代码放在一个文件里,以 API 接口的形式在其他地方调用,有利于代码的重用和降低代码耦合度。

Lua 的模块是由变量、函数等已知元素组成的 table,因此创建一个模块很简单,就是创建一个 table,然后把需要导出的常量、函数放入其中,最后返回这个 table 就行。

以下为创建自定义模块 module.lua,文件代码格式如下:

-- 文件名为 module.lua
-- 定义一个名为 module 的模块
module = {}

-- 定义一个常量
module.constant = "这是一个常量"

-- 定义一个函数
function module.func1()
io.write("这是一个公有函数!\n")
end

local function func2()
print("这是一个私有函数!")
end

function module.func3()
func2()
end

return module

由上可知,模块的结构就是一个 table 的结构,因此可以像操作调用 table 里的元素那样来操作调用模块里的常量或函数。

上面的 func2 声明为程序块的局部变量,即表示一个私有函数,因此是不能从外部访问模块里的这个私有函数,必须通过模块里的公有函数来调用

require 函数

Lua 提供了一个名为 require 的函数用来加载模块。要加载一个模块,只需要简单地调用就可以了。例如:

require("<模块名>")

或者

require "<模块名>"

执行 require 后会返回一个由模块常量或函数组成的 table,并且还会定义一个包含该 table 的全局变量。 test_module.lua 文件

-- test_module.lua 文件
-- module 模块为上文提到到 module.lua
require("module")

print(module.constant)

module.func3()

以上代码执行结果为:

这是一个常量
这是一个私有函数!

或者给加载的模块定义一个别名变量,方便调用:

-- test_module2.lua 文件
-- module 模块为上文提到到 module.lua
-- 别名变量 m
local m = require("module")

print(m.constant)

m.func3()

加载机制

如果在同一个文件夹下没有这么麻烦,直接使用 require 函数就行了

对于自定义的模块,模块文件不是放在哪个文件目录都行,函数 require 有它自己的文件路径加载策略,它会尝试从 Lua 文件或 C 程序库中加载模块。

require 用于搜索 Lua 文件的路径是存放在全局变量 package.path 中,当 Lua 启动后,会以环境变量 LUA_PATH 的值来初始这个环境变量。如果没有找到该环境变量,则使用一个编译时定义的默认路径来初始化。

当然,如果没有 LUA_PATH 这个环境变量,也可以自定义设置,在当前用户根目录下打开 .profile 文件(没有则创建,打开 .bashrc 文件也可以),例如把 "~/lua/" 路径加入 LUA_PATH 环境变量里:

#LUA_PATH
export LUA_PATH="~/lua/?.lua;;"

文件路径以 ";" 号分隔,最后的 2 个 ";;" 表示新加的路径后面加上原来的默认路径。

接着,更新环境变量参数,使之立即生效。

source ~/.profile

Reference